/*
 * Copyright (c) 2021 Huawei Device Co.,   Ltd.
 * Licensed under the Apache License,   Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,   software
 * distributed under the License is distributed on an "AS IS" BASIS,  
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,   either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "scancode_utils.h"
#include <cstdio>
#include <cstdlib>
#include <string>
#include <string.h>
#include "js_native_api.h"
#include "js_native_api_types.h"
#include "napi/native_common.h"
#include "napi/native_api.h"
#include "scancode_hilog.h"

namespace SwanLink {
namespace Finance {

AsyncCallback::AsyncCallback(napi_env env,
        size_t argc, napi_value args[],
        async_excute_work exec_func,
        async_result_work result_func,
        async_free_work free_func,
        void *data,
        napi_value this_arg, void *priv_data) {
    deferred = nullptr;
    ref_callback = nullptr;
    async_work = nullptr;
    status = napi_invalid_arg;

    data_ptr = data;
    excute_work = exec_func;
    result_work = result_func;
    free_work = free_func;
    argc_n = argc;
    DEBUG_NAPI_ASSERT_NO_RET(env, argc <= MAX_ARGS, "Wrong parameter number");

    for (size_t i = 0; i < argc; i++) {
        args_v[i] = args[i];
    }
}

AsyncCallback::~AsyncCallback() {
}

void AsyncCallback::FreeWork(napi_env env)
{
    if (free_work != nullptr)
        free_work(env, data_ptr);
    if (ref_callback != nullptr)
        napi_delete_reference(env, ref_callback);
    ref_callback = nullptr;
    if (async_work != nullptr)
        napi_delete_async_work(env, async_work);
    free_work = nullptr;
    async_work = nullptr;
    data_ptr = nullptr;
}

napi_status AsyncCallback::ExcuteWork(napi_env env)
{
    if (excute_work == nullptr)
        return napi_invalid_arg;
    return excute_work(env, argc_n, args_v, data_ptr);
}

napi_value AsyncCallback::ResultWork(napi_env env)
{
    if (result_work)
        return result_work(env, data_ptr);
    return nullptr;
}

napi_value NapiCall(const napi_env& env, AsyncCallback *async)
{
    if (async == nullptr)
        return nullptr;
    if ( napi_ok != async->ExcuteWork(env) ) {
        GET_AND_THROW_LAST_ERROR((env));
        return nullptr;
    }
    napi_value result = async->ResultWork(env);
    async->FreeWork(env);
    delete async;
    return result;
}

napi_value AsyncCall(const napi_env& env, const napi_value& callback, const std::string& res_name, AsyncCallback *async)
{
    napi_value result;
    napi_value resource;

    napi_create_string_utf8(env, res_name.c_str(),  NAPI_AUTO_LENGTH,  &resource);
    DEBUG_NAPI_CALL(env, napi_get_undefined(env,  &result));
    if (callback != nullptr) {
        DEBUG_NAPI_CALL(env, napi_create_reference(env, callback, 1, &async->ref_callback));
    }

    napi_status status = napi_create_async_work(
        env,
        nullptr,
        resource,
        [](napi_env env, void* data) {
            AsyncCallback *async = reinterpret_cast<AsyncCallback *>(data);
            async->status = async->ExcuteWork(env);

            SCANCODE_HILOGD(SCANCODE_JS_NAPI,  "async work running, %{public}d", async->status);
        },
        [](napi_env env,  napi_status status,  void *data) {
            AsyncCallback *async = reinterpret_cast<AsyncCallback *>(data);
            SCANCODE_HILOGD(SCANCODE_JS_NAPI,
                "async work begin, status:%{public}d,  p:%{public}p", status, async);
            napi_value result = nullptr;

            result = async->ResultWork(env);
            if (async->ref_callback) {
                napi_value callback = nullptr;
                napi_value parms[2];
                napi_value global;

                napi_get_reference_value(env, async->ref_callback, &callback);
                napi_create_int32(env, status, &parms[0]);
                napi_get_global(env, &global);

                parms[1] = result;
                napi_call_function(env, global, callback, 2, parms, nullptr);
            }

            async->FreeWork(env);
            delete async;
            SCANCODE_HILOGD(SCANCODE_JS_NAPI,  "async work complete");
        },
        (void *)async,
        &async->async_work);
    if (status != napi_ok) {
        SCANCODE_HILOGE(SCANCODE_JS_NAPI,  "create async work failed, %{public}d", status);
        return result;
    }
    napi_queue_async_work(env,  async->async_work);
    SCANCODE_HILOGD(SCANCODE_JS_NAPI,  "%{public}s return Promise", res_name.c_str());
    return result;
}

napi_value AsyncPromise(const napi_env& env, const std::string& res_name, AsyncCallback *async)
{
    napi_value promise;
    napi_value resource;

    napi_create_string_utf8(env, res_name.c_str(),  NAPI_AUTO_LENGTH,  &resource);
    DEBUG_NAPI_CALL(env,  napi_create_promise(env,  &async->deferred,  &promise));

    napi_status status = napi_create_async_work(
        env,
        nullptr,
        resource,
        [](napi_env env, void* data) {
            AsyncCallback *async = reinterpret_cast<AsyncCallback *>(data);
            async->status = async->ExcuteWork(env);

            SCANCODE_HILOGD(SCANCODE_JS_NAPI,  "async work running, %{public}d", async->status);
        },
        [](napi_env env,  napi_status status,  void *data) {
            AsyncCallback *async = reinterpret_cast<AsyncCallback *>(data);
            SCANCODE_HILOGD(SCANCODE_JS_NAPI,  "async work complete, status:%{public}d,  p:%{public}p",
                status, async);
            napi_value result = nullptr;

            result = async->ResultWork(env);
            if ((async->deferred != nullptr)) {
            if ((async->status == napi_ok) && (result != nullptr)) {
                napi_resolve_deferred(env,  async->deferred,  result);
            } else {
                napi_get_null(env, &result);
                napi_reject_deferred(env,  async->deferred,  result);
            }
            }

            async->FreeWork(env);
            delete async;
            SCANCODE_HILOGD(SCANCODE_JS_NAPI,  "async work complete2");
        },
        (void *)async,
        &async->async_work);
    if (status != napi_ok) {
        SCANCODE_HILOGE(SCANCODE_JS_NAPI,  "create async work failed, %{public}d", status);
        return promise;
    }
    napi_queue_async_work(env,  async->async_work);
    SCANCODE_HILOGD(SCANCODE_JS_NAPI,  "%{public}s return Promise", res_name.c_str());
    return promise;
}

// 同步
napi_value SyncPromise(const napi_env &env, napi_status status, napi_value result)
{
    napi_deferred deferred;
    napi_value  promise;
    DEBUG_NAPI_CALL(env,  napi_create_promise(env,  &deferred,  &promise));

    if (status == napi_ok) {
        napi_resolve_deferred(env,  deferred,  result);
    } else {
        napi_reject_deferred(env,  deferred,  result);
    }
    return promise;
}

}  // namespace Finance
}  // namespace SwanLink